#include "defines.h"
#include "lib.h"
#include "kozos.h"
#include "netdef.h"
#include "netlib.h"
#include "nicdrv.h"
#include "arp.h"
#include "ni.h"
#include "ip.h"

static uint8 src_IP[] = SRC_IP;
static uint8 subnet_mask[] = SUBNET_MASK;
static uint8 default_gateway[] = DEFAULT_GATEWAY;
static uint8 network_address[4];
static uint8 broadcast_address[4];

static kz_msgbox_id_t ni_mbox = MSGBOX_NULL;

typedef struct {
    uint8 IP[4];
    uint8 mask[4];
    uint8 gateway[4];
} ni_conf;

int niconf_set(uint8 IP[], int mask, uint8 gateway[])
{
    ni_conf *conf;
    uint32 a;
    int i;
    uint8 newmask[4], tmp[4];

    if (ni_mbox == MSGBOX_NULL) {
        return -1;
    }

    if (!(IP && 8 <= mask && mask <= 32 && gateway)) {
        return -1;
    }

    a = ~(uint32)0 << (32 - mask);
    newmask[0] = (uint8)(a >> 24);
    newmask[1] = (uint8)(a >> 16);
    newmask[2] = (uint8)(a >> 8);
    newmask[3] = (uint8)(a >> 0);

    // gatewayは同じネットワークの別ホストか
    if (!memcmp(IP, gateway, 4))
        return -1;
    for (i = 0; i < 4; i++) {
        if ((IP[i] ^ gateway[i]) & newmask[i])
            return -1;
    }

    // ネットワーク・ブロードキャストアドレスでないこと
    for (i = 0; i < 4; i++) {
        tmp[i] = IP[i] & newmask[i];
    }
    if (!memcmp(IP, tmp, 4) || !memcmp(gateway, tmp, 4))
        return -1;
    for (i = 0; i < 4; i++) {
        tmp[i] = IP[i] | ~newmask[i];
    }
    if (!memcmp(IP, tmp, 4) || !memcmp(gateway, tmp, 4))
        return -1;

    conf = kz_kmalloc(sizeof(ni_conf));

    memcpy(conf->IP, IP, 4);
    memcpy(conf->mask, newmask, 4);
    memcpy(conf->gateway, gateway, 4);

    kz_send(ni_mbox, 'c', (char *)conf, 0, 0);

    return 0;
}

void niconf_print()
{
    puts("IP      "); print_IP(src_IP); puts("\n");
    puts("MASK    "); print_IP(subnet_mask); puts("\n");
    puts("GATEWAY "); print_IP(default_gateway); puts("\n");
}

void send_ni(uint8 *packet)
{
    ASSERT(ni_mbox != MSGBOX_NULL, "no ni_mbox");
    if (kz_send(ni_mbox, 's', (char *)packet, 1, 5) == -1)
        kz_kmfree(packet);
}

static void set_network_broadcast()
{
    int i;
    for (i = 0; i < 4; i++) {
        network_address[i] = src_IP[i] & subnet_mask[i];
        broadcast_address[i] = src_IP[i] | ~subnet_mask[i];
    }
}

static int is_same_network(uint8 IP[])
{
    int i;
    for (i = 0; i < 4; i++) {
        if ((IP[i] & subnet_mask[i]) ^ network_address[i])
            return 0;
    }
    return 1;
}

int ni_main(int argc, char *argv[])
{
    ni_mbox = kz_getmsgbox();
    set_network_broadcast();

    kz_chpri(10); // arpスレッド(特にメッセージボックス)の初期化待ち

    arp_setIP(src_IP);

    kz_chpri(12);

    nic_use(ni_mbox, 1);

    while (1) {
        int r;
        char *buf;
        ethernet_header *ehdr;
        ip_header *ihdr;

        kz_recv(ni_mbox, &r, &buf);

        ehdr = (ethernet_header *)buf;
        ihdr = (ip_header *)(ehdr + 1);

        if (r == 'c') {
            ni_conf *conf = (ni_conf *)buf;
            memcpy(src_IP, conf->IP, 4);
            arp_setIP(src_IP);
            memcpy(subnet_mask, conf->mask, 4);
            memcpy(default_gateway, conf->gateway, 4);
            set_network_broadcast();
            kz_kmfree(conf);
        } else if (r == 's') { // ipスレッドから
            if (memeq(ihdr->ip_src_IP, 0, 4))
                memcpy(ihdr->ip_src_IP, src_IP, 4);
            ihdr->ip_checksum = ip_checksum((uint8 *)ihdr, sizeof(ip_header));
            if (!memcmp(ihdr->ip_dst_IP, src_IP, 4)) {
                ip_handle((uint8 *)buf);
            } else {
                if (!memcmp(ihdr->ip_dst_IP, network_address, 4))
                {
                    kz_kmfree(buf);
                    continue;
                }
                ehdr->eth_ethernet_type = 0x0800;
                nic_MAC(ehdr->eth_src_MAC);
                if (!memcmp(ihdr->ip_dst_IP, broadcast_address, 4)) {
                    int size = sizeof(*ehdr) + ihdr->ip_total_length;
                    uint8 *copy = kz_kmalloc(size);
                    memcpy(copy, buf, size);
                    ip_handle((uint8 *)copy);
                    memset(ehdr->eth_dst_MAC, 0xff, 6);
                    nic_send((uint8 *)buf, size);
                } else {
                    arp_resolve(is_same_network(ihdr->ip_dst_IP) ? ihdr->ip_dst_IP : default_gateway,
                                (uint8 *)buf, sizeof(*ehdr) + ihdr->ip_total_length);
                }
            }
        } else { // nicdrvから
            if (ehdr->eth_ethernet_type == 0x0806) {
                arp_handle((uint8 *)buf);
            } else if (ehdr->eth_ethernet_type == 0x0800 &&
                       (!memcmp(ihdr->ip_dst_IP, src_IP, 4) ||
                        !memcmp(ihdr->ip_dst_IP, broadcast_address, 4)))
            {
                ip_handle((uint8 *)buf);
            } else {
                kz_kmfree(buf);
            }
        }
    }

    ASSERT(0, "ni: NeverReach");
    return 0;
}
